﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;

namespace DiscoveryLogic.UI.Layouts.Controls
{
    public class NavigationManager
    {
        #region Public Events

            public event EventHandler PageChanged;

        #endregion Public Events

        #region Public Properties

            private FrameworkElement plugIn;
            private int maxNumPages;
            private Storyboard timer;
            private string pageAnimationType;
            private int pageAnimationDelta;
            private int pageAnimationTarget;
            private bool trackMovement;
            private double previousMouseMovePosition;
            private double currX1;
            private int nextOddPage;

            public int NextOddPage
            {
                get
                {
                    return nextOddPage;
                }
            }

            public double CurrX1
            {
                get
                {
                    return currX1;

                }
                set
                {
                    currX1 = value;
                }
            }

        #endregion Public Properties

        #region Constructors

            public NavigationManager(FrameworkElement plugIn, int maxNumPages)
            {
                this.plugIn = plugIn;
                this.maxNumPages = maxNumPages;

                this.timer = (Storyboard)this.plugIn.FindName("timerStoryboard");
                this.timer.Completed += new EventHandler(OnTick);

                this.pageAnimationType = "none";
                this.pageAnimationDelta = 0;
                this.pageAnimationTarget = 0;

                this.trackMovement = false;
                this.previousMouseMovePosition = 0;

                this.currX1 = 720;
                this.nextOddPage = 1;
            }

        #endregion Constructors

        #region Public Methods

            public string GetTwoDigitInt(int number)
            {
                // if this number already has two digits, return the int part
                if ((number < 0) || (number >= 10))
                    return Math.Floor(number).ToString();

                // otherwise, prepend zero
                return "0" + Math.Floor(number);
            }

            public void BeginPageAnimation(String type)
            {
                if (type == "showFold")
                {
                    if (this.nextOddPage < this.maxNumPages)
                    {
                        this.pageAnimationType = "showFold";
                        this.pageAnimationTarget = 680;
                        this.pageAnimationDelta = 5;
                        this.timer.Begin();
                    }
                }
                if (type == "hideFold")
                {
                    this.pageAnimationType = "hideFold";
                    this.pageAnimationTarget = 720;
                    this.pageAnimationDelta = (int)Math.Abs((this.currX1 - this.pageAnimationTarget));
                    this.timer.Begin();
                }
                else if (type == "finishTurn")
                {
                    if (this.nextOddPage < this.maxNumPages)
                    {
                        this.pageAnimationType = "finishTurn";
                        this.pageAnimationDelta = 10;
                        this.pageAnimationTarget = 380;
                        this.timer.Begin();
                    }
                }
                else if (type == "none")
                {
                    this.pageAnimationType = "none";
                    this.pageAnimationDelta = 0;
                    this.pageAnimationTarget = 0;
                }
            }

            public void OnAnimationComplete(String type)
            {
                if (type == "showFold")
                {
                    this.pageAnimationType = "none";
                }
                else if (type == "hideFold")
                {
                    this.currX1 = 380;
                    this.nextOddPage -= 2;
                    this.pageAnimationType = "none";
                }
                else if (type == "finishTurn")
                {
                    this.currX1 = 720;
                    this.nextOddPage += 2;
                    this.pageAnimationType = "none";
                    this.BeginPageAnimation("showFold");
                }

                if (PageChanged != null)
                {
                    try
                    {
                        PageChanged(this, null);
                    }
                    catch { } 
                     
                }

            }

            public void OnTick(object sender, EventArgs eventArgs)
            {
                // if we're animating
                if (this.pageAnimationType != "none")
                {
                    // if we are done with the current animation
                    if (this.currX1 - this.pageAnimationTarget == 0)
                    {
                        this.OnAnimationComplete(this.pageAnimationType);
                    }
                    // if we are not done with the current animation
                    else
                    {
                        // if we are within a delta of the target, draw one last frame with the final value
                        if (Math.Abs(this.currX1 - this.pageAnimationTarget) < this.pageAnimationDelta) { this.currX1 = this.pageAnimationTarget; }
                        else if (this.currX1 < this.pageAnimationTarget) { this.currX1 += this.pageAnimationDelta; }
                        else { this.currX1 -= this.pageAnimationDelta; }
                        this.timer.Begin();
                    }

                    // update the scene if possible
                    if (this.nextOddPage < this.maxNumPages)
                    {
                        this.UpdateScene(this.nextOddPage, this.currX1 - 380);
                    }
                }
            }

            public void OddPageMouseDown(object sender, EventArgs eventArgs)
            {
                Canvas c = (Canvas)sender;

                c.CaptureMouse();
                this.trackMovement = true;

                MouseEventArgs mea = eventArgs as MouseEventArgs;

                this.previousMouseMovePosition = mea.GetPosition(null).X;

                // if user clicked on a page that has fully turned
                if (!("page0" + GetTwoDigitInt(nextOddPage)).Equals(c.Name))
                {
                    if (this.nextOddPage < this.maxNumPages)
                    {
                        this.BeginPageAnimation("hideFold");
                    }
                    else
                    {
                        this.OnAnimationComplete("hideFold");
                    }
                }
                else
                {
                    this.pageAnimationType = "none";
                }
            }

            public void OddPageMouseUp(object sender, EventArgs eventArgs)
            {
                Canvas c = (Canvas)sender;

                c.ReleaseMouseCapture();
                this.trackMovement = false;

                // if we are far enough to the left, finish the turn
                if (this.currX1 < 520)
                {
                    this.BeginPageAnimation("finishTurn");
                }
                // otherwise, go back to the folded position
                else
                {
                    this.BeginPageAnimation("showFold");
                }
            }

            public void OddPageMouseMove(object sender, EventArgs eventArgs)
            {
                // if we have an animation pending, don't animate
                if ((this.trackMovement) && (this.pageAnimationType == "none"))
                {
                    MouseEventArgs mea = eventArgs as MouseEventArgs;

                    var _currDelta = ((mea.GetPosition(null).X) - this.previousMouseMovePosition) * 1.05;
                    this.previousMouseMovePosition = mea.GetPosition(null).X;

                    this.currX1 = Math.Min(720, Math.Max(380, this.currX1 + _currDelta));
                    this.UpdateScene(this.nextOddPage, this.currX1 - 380);
                }
                // if we are tracking movement but in the middle of an animation, update mouse position
                else if (this.trackMovement)
                {
                    MouseEventArgs mea = eventArgs as MouseEventArgs;
                    this.previousMouseMovePosition = mea.GetPosition(null).X;
                }
            }

            public void JumpToPage(int newOddPage)
            {
                // cancel all animations
                this.BeginPageAnimation("none");

                // goal is this.nextOddPage == newOddPage + 2
                if (this.nextOddPage == newOddPage + 2)
                    return;

                // if we need to go backwards
                if (this.nextOddPage > newOddPage + 2)
                {
                    if (this.nextOddPage > this.maxNumPages)
                        this.nextOddPage -= 2;

                    while ((this.nextOddPage > newOddPage + 2) && (this.nextOddPage >= 1))
                    {

                        this.currX1 = 720;
                        this.UpdateScene(this.nextOddPage, this.currX1 - 380);
                        this.nextOddPage -= 2;
                    }

                    // if our goal is a valid page
                    if (this.nextOddPage >= 1)
                    {
                        this.currX1 = 720;
                        this.UpdateScene(this.nextOddPage, this.currX1 - 380);
                    }
                    else
                    {
                        this.nextOddPage = 1;
                    }
                    this.BeginPageAnimation("showFold");
                }
                // if we need to go forward
                else
                {
                    while ((this.nextOddPage < newOddPage + 2) && (this.nextOddPage < this.maxNumPages))
                    {
                        this.currX1 = 380;
                        this.UpdateScene(this.nextOddPage, this.currX1 - 380);
                        this.nextOddPage += 2;
                    }

                    // if our goal is a valid page
                    if (this.nextOddPage < this.maxNumPages)
                    {
                        this.currX1 = 720;
                        this.UpdateScene(this.nextOddPage, this.currX1 - 380);
                        this.BeginPageAnimation("showFold");
                    }
                }

                if (PageChanged != null)
                {
                    try
                    {
                        PageChanged(this, null);
                    }
                    catch 
                    {
                    }
                }
            }

            public void UpdateScene(int oddPageNumber, double x1)
            {
                // variables related to odd page
                LineSegment oddPoint1 = (LineSegment)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "Point1");
                LineSegment oddPoint2 = (LineSegment)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "Point2");
                LineSegment oddPoint3 = (LineSegment)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "Point3");

                RotateTransform oddRotate = (RotateTransform)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "Rotate");
                TranslateTransform oddTranslate = (TranslateTransform)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "Translate");
                Rectangle foldShadow = (Rectangle)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "FoldShadow");
                RotateTransform foldShadowRotate = (RotateTransform)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "FoldShadowRotate");
                TranslateTransform foldShadowTranslate = (TranslateTransform)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber) + "FoldShadowTranslate");

                Image shadowBehindPage01 = (Image)this.plugIn.FindName("shadowBehindPage01");

                // variables related to the even page
                LineSegment evenPoint1 = (LineSegment)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber - 1) + "Point1");
                LineSegment evenPoint2 = (LineSegment)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber - 1) + "Point2");
                LineSegment evenPoint3 = (LineSegment)this.plugIn.FindName("page" + GetTwoDigitInt(oddPageNumber - 1) + "Point3");

                Polygon shadowOnEvenPage = (Polygon)this.plugIn.FindName("shadowOnEvenPage");
                Point[] shadowOnEvenPagePoints = new Point[4];

                // _alpha: angle between horizontal axis and bottom edge of odd page.
                //         this can be any function of x1.
                //double _alpha = 90 / 340 * x1;
                double _alpha = 90 * (x1 / 340);

                // update point 1 of even page
                evenPoint1.Point = new Point(x1, 430);

                shadowOnEvenPagePoints[0] = new Point(x1, 430);
                shadowOnEvenPagePoints[1] = new Point(Math.Min((x1 + 30), 340), 430);

                // _leftEdgeAngle: angle between horizontal axis and left edge of odd page
                var _leftEdgeAngle = 90 - _alpha;
                var _bottomLeftCornerX = x1 - Math.Cos(_alpha * Math.PI / 180) * (340 - x1);
                var _bottomLeftCornerY = Math.Sin(_alpha * Math.PI / 180) * (340 - x1);

                // update odd page's rotate and translate transform
                oddRotate.Angle = _alpha;
                oddTranslate.X = 340 + _bottomLeftCornerX;
                oddTranslate.Y = (-1) * _bottomLeftCornerY;

                // how much of the odd page's left edge can be seen
                var _visibleLeftEdgeHeight = (340 - _bottomLeftCornerX) / Math.Cos(_leftEdgeAngle * Math.PI / 180);

                // if the top left corner can be seen
                if (_visibleLeftEdgeHeight >= 430)
                {
                    // height between top left corner of the odd page and top edge of the even page
                    var _topLeftCornerY = _bottomLeftCornerY + Math.Sin(_leftEdgeAngle * Math.PI / 180) * 430;

                    // _visibleTopEdgeWidth: width of top edge of odd page that is visible
                    double _visibleTopEdgeWidth;

                    // _x2: x coordinate of the point where top edges of odd and even pages intersect
                    double _x2;

                    if (_topLeftCornerY > 430)
                    {
                        _visibleTopEdgeWidth = (_topLeftCornerY - 430) / Math.Sin(_alpha * Math.PI / 180);
                        _x2 = _bottomLeftCornerX + Math.Cos(_leftEdgeAngle * Math.PI / 180) * 430 + (_topLeftCornerY - 430) / Math.Tan(_alpha * Math.PI / 180);
                    }
                    else
                    {
                        _visibleTopEdgeWidth = 340;
                        _x2 = 0;
                    }

                    oddPoint1.Point = new Point(0, 0);
                    oddPoint2.Point = new Point(_visibleTopEdgeWidth, 0);

                    // update foldShadow properties
                    var _foldAngle = 90 - (180 / Math.PI) * Math.Atan(430 / (340 - x1 - _visibleTopEdgeWidth));
                    foldShadowRotate.Angle = (-1) * _foldAngle;

                    // need to adjust the position of the foldShadow so that it covers the entire folded edge
                    var _foldShadowAdjustment = 40;
                    foldShadowTranslate.X = _visibleTopEdgeWidth - 20 * Math.Cos(_foldAngle * Math.PI / 180) - _foldShadowAdjustment * Math.Cos((90 - _foldAngle) * Math.PI / 180);
                    foldShadowTranslate.Y = 20 * Math.Sin(_foldAngle * Math.PI / 180) - _foldShadowAdjustment * Math.Sin((90 - _foldAngle) * Math.PI / 180); ;

                    evenPoint2.Point = new Point(_x2, 0);
                    evenPoint3.Point = new Point(_x2, 0);

                    shadowOnEvenPagePoints[2] = new Point(Math.Min(_x2 + 10, 340), 0);
                    shadowOnEvenPagePoints[3] = new Point(_x2, 0);

                }
                // if the top left corner cannot be seen
                else
                {
                    // update points of the odd page, which are both the same
                    oddPoint1.Point = new Point(0, (430 - _visibleLeftEdgeHeight));
                    oddPoint2.Point = new Point(0, (430 - _visibleLeftEdgeHeight));

                    // update points of the even page
                    var _y2 = 430 - _bottomLeftCornerY - (340 - _bottomLeftCornerX) * Math.Tan(_leftEdgeAngle * Math.PI / 180);
                    evenPoint2.Point = new Point(340, _y2);
                    evenPoint3.Point = new Point(340, 0);

                    // update shadow string on top of the even page
                    shadowOnEvenPagePoints[2] = new Point(340, _y2);
                    shadowOnEvenPagePoints[3] = new Point(340, _y2);

                    // update foldShadow properties
                    double _foldAngle;
                    if (x1 != 340)
                    {
                        _foldAngle = 90 - (180 / Math.PI) * Math.Atan(_visibleLeftEdgeHeight / (340 - x1));
                    }
                    else
                    {
                        //the arctangent of infinity is 90
                        _foldAngle = 0;
                    }

                    foldShadowRotate.Angle = (-1) * _foldAngle;
                    foldShadowTranslate.X = (-1) * 20 * Math.Cos(_foldAngle * Math.PI / 180);
                    foldShadowTranslate.Y = (430 - _visibleLeftEdgeHeight) + 20 * Math.Sin(_foldAngle * Math.PI / 180);
                }

                oddPoint3.Point = new Point((340 - x1), 430);

                // shadowOnEvenPage.Points = shadowOnEvenPagePoints;
                PointCollection pc = new PointCollection();
                for (int i = 0; i < shadowOnEvenPagePoints.Length; i++)
                {
                    pc.Add(shadowOnEvenPagePoints[i]);
                }

                shadowOnEvenPage.Points = pc;

                if (x1 < 15)
                {
                    shadowOnEvenPage.Opacity = 0.25 * (x1 / 15);

                    if (oddPageNumber > 1) { shadowBehindPage01.Opacity = 0.8; }
                    else { shadowBehindPage01.Opacity = 0.8 - 0.8 * (x1 / 15); }
                }
                else
                {
                    shadowOnEvenPage.Opacity = 0.25;

                    foldShadow.Opacity = 0.6;

                    if (oddPageNumber > 1) { shadowBehindPage01.Opacity = 0.8; }
                    else { shadowBehindPage01.Opacity = 0; }
                }
            }

        #endregion Public Methods
    }
}
